//
// Book:      OpenGL(R) ES 2.0 Programming Guide
// Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner
// ISBN-10:   0321502795
// ISBN-13:   9780321502797
// Publisher: Addison-Wesley Professional
// URLs:      http://safari.informit.com/9780321563835
//            http://www.opengles-book.com
// Additional contributions copyright (c) 2011 Research In Motion Limited

// ParticleSystem.c
//
//    This is an example that demonstrates rendering a particle system
//    using a vertex shader and point sprites.
//
#include <stdlib.h>
#include <math.h>
#include "esUtil.h"

#define NUM_PARTICLES   1000
#define PARTICLE_SIZE   7

typedef struct
{
    // Handle to a program object
    GLuint programObject;

    // Attribute locations
    GLint lifetimeLoc;
    GLint startPositionLoc;
    GLint endPositionLoc;

    // Uniform location
    GLint timeLoc;
    GLint colorLoc;
    GLint centerPositionLoc;
    GLint samplerLoc;

    // Texture handle
    GLuint textureId;

    // Particle vertex data
    float particleData[NUM_PARTICLES * PARTICLE_SIZE];

    // Current time
    float time;

} UserData;

///
// Load texture from disk
//
GLuint LoadTexture(char *fileName)
{
    int width, height;
    char *buffer = esLoadTGA(fileName, &width, &height);
    GLuint texId;

    if (buffer == NULL)
    {
        esLogMessage("Error loading (%s) image.\n", fileName);
        return 0;
    }

    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    free(buffer);

    return texId;
}


///
// Initialize the shader and program object
//
int Init(ESContext *esContext)
{
    UserData *userData = esContext->userData;
    int i;

    GLchar vShaderStr[] =
        "uniform float u_time;                                \n"
        "uniform vec3 u_centerPosition;                       \n"
        "attribute float a_lifetime;                          \n"
        "attribute vec3 a_startPosition;                      \n"
        "attribute vec3 a_endPosition;                        \n"
        "varying float v_lifetime;                            \n"
        "void main()                                          \n"
        "{                                                    \n"
        "  if ( u_time <= a_lifetime )                        \n"
        "  {                                                  \n"
        "    gl_Position.xyz = a_startPosition +              \n"
        "                      (u_time * a_endPosition);      \n"
        "    gl_Position.xyz += u_centerPosition;             \n"
        "    gl_Position.w = 1.0;                             \n"
        "  }                                                  \n"
        "  else                                               \n"
        "     gl_Position = vec4( -1000, -1000, 0, 0 );       \n"
        "  v_lifetime = 1.0 - ( u_time / a_lifetime );        \n"
        "  v_lifetime = clamp ( v_lifetime, 0.0, 1.0 );       \n"
        "  gl_PointSize = ( v_lifetime * v_lifetime ) * 40.0; \n"
        "}";

    GLchar fShaderStr[] =
        "precision mediump float;                             \n"
        "uniform vec4 u_color;                                \n"
        "varying float v_lifetime;                            \n"
        "uniform sampler2D s_texture;                         \n"
        "void main()                                          \n"
        "{                                                    \n"
        "  vec4 texColor;                                     \n"
        "  texColor = texture2D( s_texture, gl_PointCoord );  \n"
        "  gl_FragColor = vec4( u_color ) * texColor;         \n"
        "  gl_FragColor.a *= v_lifetime;                      \n"
        "}                                                    \n";

    // Load the shaders and get a linked program object
    userData->programObject = esLoadProgram(vShaderStr, fShaderStr);

    // Get the attribute locations
    userData->lifetimeLoc = glGetAttribLocation(userData->programObject, "a_lifetime");
    userData->startPositionLoc = glGetAttribLocation(userData->programObject, "a_startPosition");
    userData->endPositionLoc = glGetAttribLocation(userData->programObject, "a_endPosition");

    // Get the uniform locations
    userData->timeLoc = glGetUniformLocation(userData->programObject, "u_time");
    userData->centerPositionLoc = glGetUniformLocation(userData->programObject, "u_centerPosition");
    userData->colorLoc = glGetUniformLocation(userData->programObject, "u_color");
    userData->samplerLoc = glGetUniformLocation(userData->programObject, "s_texture");

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    // Fill in particle data array
    srand(0);
    for (i = 0; i < NUM_PARTICLES; i++)
    {
        float *particleData = &userData->particleData[i * PARTICLE_SIZE];

        // Lifetime of particle
        (*particleData++) = ((float) (rand() % 10000) / 10000.0f);

        // End position of particle
        (*particleData++) = ((float) (rand() % 10000) / 5000.0f) - 1.0f;
        (*particleData++) = ((float) (rand() % 10000) / 5000.0f) - 1.0f;
        (*particleData++) = ((float) (rand() % 10000) / 5000.0f) - 1.0f;

        // Start position of particle
        (*particleData++) = ((float) (rand() % 10000) / 40000.0f) - 0.125f;
        (*particleData++) = ((float) (rand() % 10000) / 40000.0f) - 0.125f;
        (*particleData++) = ((float) (rand() % 10000) / 40000.0f) - 0.125f;

    }

    // Initialize time to cause reset on first update
    userData->time = 1.0f;

    userData->textureId = LoadTexture("res/textures/smoke.tga");
    if (userData->textureId <= 0)
    {
        return FALSE;
    }

    return TRUE;
}

///
//  Update time-based variables
//
void Update ( ESContext *esContext, float deltaTime )
{
    UserData *userData = esContext->userData;

    userData->time += deltaTime;

    if ( userData->time >= 1.0f )
    {
        float centerPos[3];
        float color[4];

        userData->time = 0.0f;

        // Pick a new start location and color
        centerPos[0] = ( (float)(rand() % 10000) / 10000.0f ) - 0.5f;
        centerPos[1] = ( (float)(rand() % 10000) / 10000.0f ) - 0.5f;
        centerPos[2] = ( (float)(rand() % 10000) / 10000.0f ) - 0.5f;

        glUniform3fv ( userData->centerPositionLoc, 1, &centerPos[0] );

        // Random color
        color[0] = ( (float)(rand() % 10000) / 20000.0f ) + 0.5f;
        color[1] = ( (float)(rand() % 10000) / 20000.0f ) + 0.5f;
        color[2] = ( (float)(rand() % 10000) / 20000.0f ) + 0.5f;
        color[3] = 0.5;

        glUniform4fv ( userData->colorLoc, 1, &color[0] );
    }

    // Load uniform time variable
    glUniform1f ( userData->timeLoc, userData->time );
}

///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( ESContext *esContext )
{
    UserData *userData = esContext->userData;

    // Set the viewport
    glViewport ( 0, 0, esContext->width, esContext->height );

    // Clear the color buffer
    glClear ( GL_COLOR_BUFFER_BIT );

    // Use the program object
    glUseProgram ( userData->programObject );

    // Load the vertex attributes
    glVertexAttribPointer ( userData->lifetimeLoc, 1, GL_FLOAT,
                           GL_FALSE, PARTICLE_SIZE * sizeof(GLfloat),
                           userData->particleData );

    glVertexAttribPointer ( userData->endPositionLoc, 3, GL_FLOAT,
                           GL_FALSE, PARTICLE_SIZE * sizeof(GLfloat),
                           &userData->particleData[1] );

    glVertexAttribPointer ( userData->startPositionLoc, 3, GL_FLOAT,
                           GL_FALSE, PARTICLE_SIZE * sizeof(GLfloat),
                           &userData->particleData[4] );


    glEnableVertexAttribArray ( userData->lifetimeLoc );
    glEnableVertexAttribArray ( userData->endPositionLoc );
    glEnableVertexAttribArray ( userData->startPositionLoc );
    // Blend particles
    glEnable ( GL_BLEND );
    glBlendFunc ( GL_SRC_ALPHA, GL_ONE );

    // Bind the texture
    glActiveTexture ( GL_TEXTURE0 );
    glBindTexture ( GL_TEXTURE_2D, userData->textureId );
    glEnable ( GL_TEXTURE_2D );

    // Set the sampler texture unit to 0
    glUniform1i ( userData->samplerLoc, 0 );

    glDrawArrays( GL_POINTS, 0, NUM_PARTICLES );

    eglSwapBuffers ( esContext->eglDisplay, esContext->eglSurface );
}

///
// Cleanup
//
void ShutDown ( ESContext *esContext )
{
    UserData *userData = esContext->userData;

    // Delete texture object
    glDeleteTextures ( 1, &userData->textureId );

    // Delete program object
    glDeleteProgram ( userData->programObject );
}


int main ( int argc, char *argv[] )
{
    ESContext esContext;
    UserData  userData;

    esInitContext ( &esContext );
    esContext.userData = &userData;

    esCreateWindow ( &esContext, "ParticleSystem", 1024, 600, ES_WINDOW_RGB );

    if ( !Init ( &esContext ) )
        return 0;

    esRegisterDrawFunc ( &esContext, Draw );
    esRegisterUpdateFunc ( &esContext, Update );

    esMainLoop ( &esContext );

    ShutDown ( &esContext );

    return 0;
}
